package viewservice

import "net"
import "net/rpc"
import "log"
import "time"
import "sync"
import "fmt"
import "os"
import (
	"sync/atomic"
)

type ViewServer struct {
	mu       sync.Mutex
	l        net.Listener
	dead     int32 // for testing
	rpccount int32 // for testing
	me       string

	Primary *Server
	Backup  *Server
	Idle    *Server
	Viewnum uint
	// Your declarations here.
}

type Server struct {
	Tick  uint
	Name  string
	Acked bool
}

func (s *Server) reset() {
	s.Name = ""
	s.Tick = 0
	s.Acked = false
}

func (s *Server) update(name string) {
	if name != s.Name {
		s.Name = name
		s.Tick = 0
		s.Acked = false
	}
}

func (s *Server) isEmpty() bool {
	return s.Name == ""
}

func (s *Server) incTicket() {
	if !s.isEmpty() && s.Acked {
		s.Tick = s.Tick + 1
	}
}

func (s *Server) decTicket() {
	if !s.isEmpty() && s.Tick != 0 {
		s.Tick = s.Tick - 1
	}
}

func (s *Server) isSame(other *Server) bool {
	return s.Name == other.Name
}

func (s *Server) isDead() bool {
	return !s.isEmpty() && s.Tick >= DeadPings
}
func (vs *ViewServer) setView(view *View) {
	view.Primary = vs.Primary.Name
	view.Backup = vs.Backup.Name
	view.Viewnum = vs.Viewnum
}

//
// server Ping RPC handler.
//
func (vs *ViewServer) Ping(args *PingArgs, reply *PingReply) error {

	if args.Me == vs.Primary.Name && args.Viewnum == vs.Viewnum {
		vs.Primary.Acked = true
	}

	if !vs.Primary.isEmpty() && !vs.Primary.Acked {
		vs.setView(&reply.View)
		return nil
	}

	if vs.Primary.isEmpty() && vs.Backup.isEmpty() && vs.Viewnum <= 1 {
		vs.incViewNum()
		vs.Primary.update(args.Me)
	} else {
		switch args.Me {
		case vs.Primary.Name:
			vs.Primary.decTicket()
			if args.Viewnum == 0 {
				vs.incViewNum()
				if vs.Backup.isEmpty() {
					vs.Primary.update(args.Me)
				} else {
					vs.Backup, vs.Primary = vs.Primary, vs.Backup
					vs.Primary.Acked = false
					vs.Backup.Acked = true
				}
			}
		case vs.Backup.Name:
			vs.Backup.decTicket()
		case vs.Idle.Name:
			vs.Idle.decTicket()
		default:
			if vs.Backup.isEmpty() {
				vs.incViewNum()
				vs.Backup.update(args.Me)
				vs.Backup.Acked = true
				vs.Primary.Acked = false
			} else if vs.Idle.isEmpty() {
				vs.Idle.update(args.Me)
			}

		}
	}
	vs.setView(&reply.View)
	return nil
}

//
// server Get() RPC handler.
//
func (vs *ViewServer) Get(args *GetArgs, reply *GetReply) error {
	vs.setView(&reply.View)
	return nil
}

//
// tick() is called once per PingInterval; it should notice
// if servers have died or recovered, and change the view
// accordingly.
//
func (vs *ViewServer) tick() {

	if !vs.Primary.Acked {
		return
	}

	// Your code here.
	vs.Primary.incTicket()
	vs.Backup.incTicket()
	vs.Idle.incTicket()

	if vs.Idle.isDead() {
		vs.Idle.reset()
	}

	if vs.Primary.isDead() && vs.Backup.isDead() {
		vs.incViewNum()
		vs.Primary.reset()
		vs.Backup.reset()
		return
	}
	if vs.Primary.isDead() {
		vs.incViewNum()
		vs.Primary, vs.Backup = vs.Backup, vs.Primary
		vs.Backup.reset()
		vs.Primary.Acked = false
		vs.Backup.Acked = true
	} else if vs.Backup.isDead() {
		vs.incViewNum()
		vs.Backup.reset()
	}

	if vs.Backup.isEmpty() && !vs.Idle.isEmpty() {
		vs.Backup, vs.Idle = vs.Idle, vs.Backup
		vs.Backup.Acked = true
	}
}

func (vs *ViewServer) incViewNum() {
	vs.Viewnum = vs.Viewnum + 1
	if !vs.Primary.isEmpty() {
		vs.Primary.Acked = false
	}
}

//
// tell the server to shut itself down.
// for testing.
// please don't change these two functions.
//
func (vs *ViewServer) Kill() {
	atomic.StoreInt32(&vs.dead, 1)
	vs.l.Close()
}

//
// has this server been asked to shut down?
//
func (vs *ViewServer) isdead() bool {
	return atomic.LoadInt32(&vs.dead) != 0
}

// please don't change this function.
func (vs *ViewServer) GetRPCCount() int32 {
	return atomic.LoadInt32(&vs.rpccount)
}

func StartServer(me string) *ViewServer {
	vs := new(ViewServer)
	vs.me = me
	vs.Primary = &Server{}
	vs.Primary.reset()

	vs.Backup = &Server{}
	vs.Backup.reset()

	vs.Idle = &Server{}
	vs.Idle.reset()

	vs.Viewnum = 0
	// Your vs.* initializations here.

	// tell net/rpc about our RPC server and handlers.
	rpcs := rpc.NewServer()
	rpcs.Register(vs)

	// prepare to receive connections from clients.
	// change "unix" to "tcp" to use over a network.
	os.Remove(vs.me) // only needed for "unix"
	l, e := net.Listen("unix", vs.me)
	if e != nil {
		log.Fatal("listen error: ", e)
	}
	vs.l = l

	// please don't change any of the following code,
	// or do anything to subvert it.

	// create a thread to accept RPC connections from clients.
	go func() {
		for vs.isdead() == false {
			conn, err := vs.l.Accept()
			if err == nil && vs.isdead() == false {
				atomic.AddInt32(&vs.rpccount, 1)
				go rpcs.ServeConn(conn)
			} else if err == nil {
				conn.Close()
			}
			if err != nil && vs.isdead() == false {
				fmt.Printf("ViewServer(%v) accept: %v\n", me, err.Error())
				vs.Kill()
			}
		}
	}()

	// create a thread to call tick() periodically.
	go func() {
		for vs.isdead() == false {
			vs.tick()
			time.Sleep(PingInterval)
		}
	}()

	return vs
}
